/*
 * Scanner.java
 *
 * Created on November 3, 2007, 7:20 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.agentfactory.platform.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

/**
 * This class provides support for parsing of textual content.  It breaks the
 * text up in to a set of statements that are delimited by a carriage return,
 * such as:
 *
 * COMMAND arg1 arg2 ...
 *
 * TO DO (Rem Collier): Implement support for multi-line statements that take
 * the form:
 *
 * COMMAND {
 * arg1
 * arg2
 * }
 *
 * NOTE: In this revised format, each argument in the statement will be
 * delimited based only on the end of line character.
 *
 * Finally, the scanner is designed to ignore both single line and multi
 * line comments (based on Java style syntax).
 *
 * @author remcollier
 * @version $Id: Scanner.java,v 1.5 2007/12/11 12:57:42 remcollier Exp $
 */
public class Scanner {
    private static final int CLASS_LOG_LEVEL                = Logger.LEVEL_ERROR;
    
    private BufferedReader in;
    private boolean finished;

    /**
     * Create a scanner based on an input stream by wrapping the InputStream
     * object in an InputStreamReader.
     *
     * @param in the InputStream object.
     */
    public Scanner(InputStream in) {
        this(new InputStreamReader(in));
    }

    /**
     * Create a scanner based on some text by wrapping the text in a StringReader.
     *
     * @param text the text that the scanner is to scan.
     */
    public Scanner(String text) {
        this(new StringReader(text));
    }

    /**
     * Create a scanner based a Reader object. This constructor is invoked by
     * all other class constructors and wraps the Reader object in a
     * BufferedReader.
     *
     * @param in the Reader object whose output is to be scanned.
     */
    public Scanner(Reader in) {
        this.in = new BufferedReader(in);
        finished = false;
    }

    /**
     * This method reads from the BufferedReader, character by character,
     * generating a list of tokens that represent a single statement from
     * the file (based on the syntax requirements laid down in the class
     * comment).
     *
     * @return Returns a list of strings that represent a single statement from
     * the file.
     */
    public List nextStatement() throws IOException {
        List list = new ArrayList();

        // Flags for handling comments
        boolean multiLineComment = false;
        boolean singleLineComment = false;

        // Token buffer & end of statement flag
        StringBuffer buf = new StringBuffer();
        boolean endOfStatement = false;
        
        while (!endOfStatement) {
            int ch = in.read();
                        
            if (multiLineComment) {
                if (ch == '*') {
                    ch = in.read();
                    if (ch == '/') multiLineComment = false;
                }
            } else if (singleLineComment) {
                if (ch == '\n') singleLineComment = false;
            } else {
                switch (ch) {
                    case '/':
                        ch = in.read();
                        if (ch == '*') {
                            multiLineComment = true;
                        } else if (ch == '/') {
                            if (buf.length() == 0) { 
                                singleLineComment = true;
                            } else {
                                buf.append('/').append((char) ch);
                            }
                        } else {
                            buf.append('/').append((char) ch);
                        }
                        break;
                    case '\n':
                        endOfStatement = true;
                    case ' ':
                        if (buf.length() > 0) list.add(buf.toString());
                        buf = new StringBuffer();
                        break;
                    case '\0':
                    case -1:
                        endOfStatement = true;
                        finished = true;
                        if (buf.length() > 0) list.add(buf.toString());
                        break;
                    default:
                        buf.append((char) ch);
                }
            }
        }
        
        return list;
    }

    /**
     * Checks whether or not the scanner has any more statements
     *
     * @return true if the scanner is not finished, false otherwise
     */
    public boolean hasNext() {
        return !finished;
    }

    /**
     * close the underlying BufferedReader
     */
    public void close() {
        try {
            in.close();
        } catch (IOException e) {
            // IGNORE for now
        }
    }
}
